<?php

// +---------------------------------------------------------------------- 
// RSA算法属于非对称加密算法,非对称加密算法需要两个秘钥:公开密钥(publickey)和私有秘钥(privatekey).公开密钥和私有秘钥是一对,如果公开密钥对数据进行加密,只有用对应的私有秘钥才能解密;如果私有秘钥对数据进行加密那么只有用对应的公开密钥才能解密.因为加密解密使用的是两个不同的秘钥,所以这种算法叫做非对称加密算法.简单的说就是公钥加密私钥解密,私钥加密公钥解密.
// 需要给PHP打开OpenSSL模块
// 生成公钥和私钥的链接:  http://web.chacuo.net/netrsakeypair
// openssl_pkey_get_public //检查公钥是否可用
// openssl_public_encrypt //公钥加密
// openssl_pkey_get_private //检查私钥是否可用
// openssl_private_decrypt //私钥解密 
// +----------------------------------------------------------------------
// | Copyright (c) 2015-2024 http://www.yicmf.com, All rights reserved.
// +----------------------------------------------------------------------
// | Author: 微尘 <yicmf@qq.com>
// +----------------------------------------------------------------------

namespace yicmf\tools;

use think\Exception;

class Rsa
{
    private $_config = [
        'public_key' => '',
        'private_key' => '',
    ];


    public function __construct($private_key = null, $public_key = null)
    {
        if (!is_null($private_key)) {
            if (is_file($private_key)) {
                $this->_config['private_key'] = $this->_getContents($private_key);
            } elseif (strpos($private_key, '-BEGIN')) {
                $this->_config['private_key'] = $private_key;
            } else {
                $this->setPrivateKey($private_key);
            }
        }
        if (!is_null($public_key)) {
            if (is_file($public_key)) {
                $this->_config['public_key'] = $this->_getContents($public_key);
            } elseif (strpos($public_key, '-BEGIN')) {
                $this->_config['public_key'] = $public_key;
            } else {
                $this->setPublicKey($public_key);
            }
        }
    }

    public function setPublicKey($public_key)
    {
        $public_key = "-----BEGIN PUBLIC KEY-----\n" .
            wordwrap($public_key, 64, "\n", true)
            . "\n-----END PUBLIC KEY-----";
        $this->_config['public_key'] = $public_key;
        return $this;
    }

    public function setPrivateKey($private_key)
    {

        $private_key = "-----BEGIN RSA PRIVATE KEY-----\n" .
            wordwrap($private_key, 64, "\n", true)
            . "\n-----END RSA PRIVATE KEY-----";

        $this->_config['private_key'] = $private_key;
        return $this;
    }

    public function getKeyLen()
    {
        $details = openssl_pkey_get_details($this->_getPublicKey());
        return $details['bits'];
    }

    /**
     * @param string $data
     * @return null|string
     * @uses 私钥加密
     */
    public function privateEncrypt(string $data = ''): ?string
    {
        if (!is_string($data)) {
            return null;
        }

        $part_len = $this->getKeyLen() / 8 - 11;

        $crypto = '';
        foreach (str_split($data, 117) as $chunk) {
            openssl_private_encrypt($chunk, $encrypted, $this->_getPrivateKey());
            $crypto .= $encrypted;
        }
        return $crypto ? base64_encode($crypto) : null;
    }

    /**
     * @param string $data
     * @return null|string
     * @uses 公钥加密
     */
    public function publicEncrypt(string $data = ''): ?string
    {
        if (!is_string($data)) {
            return null;
        }
        $crypto = '';
        $part_len = $this->getKeyLen() / 8 - 11;
        foreach (str_split($data, $part_len) as $chunk) {
            openssl_public_encrypt($chunk, $encrypted, $this->_getPublicKey());
            $crypto .= $encrypted;
        }
        return $crypto ? base64_encode($crypto) : null;
    }

    /**
     * @param string $encrypted
     * @return null
     * @uses 私钥解密
     */
    public function privateDecrypt($encrypted = '')
    {
        $private_key = $this->_getPrivateKey();
        if (!$private_key) {
            throw new Exception('私钥不可用');
        }
        $public_check = $this->_getPublicKey();
        if (!$public_check) {
            throw new Exception('私钥不可用');
        }
        $details = openssl_pkey_get_details($public_check);

        $decrypted = '';
        $base64_decoded = self::safe_base64_decode($encrypted);
        // 分段解密
        $parts = str_split($base64_decoded, ($this->getKeyLen() / 8));
        foreach ($parts as $part) {
            $decrypted_temp = '';
            $decrypt_res = openssl_private_decrypt($part, $decrypted_temp, $private_key);
            if (!$decrypt_res) {
                throw new Exception('解密失败,请检查RSA秘钥');
            }
            $decrypted .= $decrypted_temp;
        }
        return $decrypted;
    }

    /**
     * @param string $encrypted
     * @return null
     * @uses 公钥解密
     */
    public function publicDecrypt($encrypted = '')
    {
        if (!$this->_getPrivateKey()) {
            throw new Exception('私钥不可用');
        }
        $public_check = $this->_getPublicKey();
        if (!$this->_getPublicKey()) {
            throw new Exception('公钥不可用');
        }
        $details = openssl_pkey_get_details($public_check);

        $decrypted = '';
        $base64_decoded = self::safe_base64_decode($encrypted);

        $parts = str_split($base64_decoded, ($this->getKeyLen() / 8));
        foreach ($parts as $part) {
            $decrypted_temp = '';
            $decrypt_res = openssl_public_decrypt($part, $decrypted_temp, $this->_getPublicKey());
            if (!$decrypt_res) {
                throw new Exception('解密失败,请检查RSA秘钥');
            }
            $decrypted .= $decrypted_temp;
        }

        return $decrypted;
    }

    /**
     * @function 私钥加签
     * @param $data
     * @return string|null
     */
    public function sign($data)
    {
        if (!is_string($data)) {
            return null;
        }
        openssl_sign($data, $sign, $this->_getPrivateKey(), OPENSSL_ALGO_SHA256);
        //base64编码
        $sign = base64_encode($sign);
        return $sign;
    }

    /**
     * @function 公钥验签
     * @param $data
     * @return string|null
     */
    public function verify($data, $sign)
    {
        if (!is_string($data)) {
            return null;
        }
        $result = (bool)openssl_verify($data, base64_decode($sign), $this->_getPublicKey(), OPENSSL_ALGO_SHA256);
        return $result;
    }

    /**
     * 创建密钥对
     * @return array
     */
    public static function build(): array
    {
// 配置
        $config = [
            'digest_alg' => 'sha256',
            'private_key_bits' => 2048,
            'private_key_type' => OPENSSL_KEYTYPE_RSA,
        ];
// 创建私钥
        $res = openssl_pkey_new($config);
        $private = '';
// 提取私钥到$privKey
        openssl_pkey_export($res, $private);
// 提取公钥到$pubKey
        $pubKey = openssl_pkey_get_details($res);
        return ['private' => $private, 'public' => $pubKey['key']];
    }

    /**
     * base64解码
     * @param unknown $string
     */
    private static function safe_base64_decode($string)
    {
        $base_64 = str_replace(array('-', '_'), array('+', '/'), $string);
        return base64_decode($base_64);
    }


    /**
     * @param $file_path string
     * @return bool|string
     * @uses 获取文件内容
     */
    private function _getContents(string $file_path)
    {
        if (!file_exists($file_path)) {
            throw new Exception('密钥或公钥的文件路径错误');
        };
        return file_get_contents($file_path);
    }

    /**
     * @return bool|resource
     * @uses 获取私钥
     */
    private function _getPrivateKey()
    {
        return openssl_pkey_get_private($this->_config['private_key']);
    }

    /**
     * @return bool|resource
     * @uses 获取公钥
     */
    private function _getPublicKey()
    {
        return openssl_pkey_get_public($this->_config['public_key']);
    }

}